#include <jni.h>
#include <string>
#include <rtmp.h> // 查找系统的环境变量 <>
#include <x264.h> // 查找系统的环境变量 <>
#include "VideoChannel.h"
#include "util.h"
#include "SafeQueue.h"
#include "AudioChannel.h"

VideoChannel *videoChannel = nullptr;
AudioChannel * audioChannel = nullptr;
bool isStart; // 标记是否开始直播
pthread_t pid_start;
bool readyPushing;
SafeQueue<RTMPPacket *> packets; // 不区分音频和视频，（音频 & 视频）一直存储，  start直接拿出去发送给流媒体服务器（添加到队列中的是压缩包）
uint32_t start_time; // 记录时间搓，为了计算下，时间搓

// videoCallback 函数指针的实现（将编码后数据存放packet到队列）
void callback(RTMPPacket *packet) {
    if (packet) {
        if (packet->m_nTimeStamp == -1) {
            packet->m_nTimeStamp = RTMP_GetTime() - start_time; // 如果是sps+pps 没有时间搓，如果是I帧就需要有时间搓
        }
        packets.push(packet); // 存入队列里面
    }
}

// 释放RTMPPacket * 包的函数指针实现，T无法释放， 让外界释放
void releasePackets(RTMPPacket **packet) {
    if (packet) {
        RTMPPacket_Free(*packet);
        delete packet;
        packet = nullptr;
    }
}

/**
 * NdkPusher构造函数调用的 ---> 初始化工作
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_ndk_push_NdkPusher_native_1init(JNIEnv *env, jobject thiz) {
    // 初始化 VideoChannel 视频通道
    videoChannel = new VideoChannel();
    // 设置 Camera预览画面的数据推送到native层，videoChannel编码后数据，通过callback回调到native-lib.cpp，加入队列
    videoChannel->setVideoCallback(callback);
    // 初始化 AudioChannel 音频通道
    audioChannel = new AudioChannel();
    // 设置 AudioRecord采集录制音频数据推送到native层，audioChannel编码后数据，通过callback回调到native-lib.cpp，加入队列
    audioChannel->setAudioCallback(callback);
    // 设置 队列的释放工作 回调
    packets.setReleaseCallback(releasePackets);
}

/**
 * 连接RTMP服务器，遍历压缩包队列，将数据发送到RTMP服务器
 * @param args url
 * @return
 */
void *task_start(void *args) {
    char *url = static_cast<char *>(args);
    // RTMPDump API 九部曲
    RTMP *rtmp = nullptr;
    int result; // 返回值判断成功失败
    do {
        // 1.1，rtmp 初始化
        rtmp = RTMP_Alloc();
        if (!rtmp) {
            LOGE("rtmp 初始化失败");
            break;
        }
        // 1.2，rtmp 初始化
        RTMP_Init(rtmp);
        rtmp->Link.timeout = 5; // 设置连接的超时时间（以秒为单位的连接超时）
        // 2，rtmp 设置流媒体地址
        result = RTMP_SetupURL(rtmp, url);
        if (!result) { // result == 0 和 ffmpeg不同，0代表失败
            LOGE("rtmp 设置流媒体地址失败");
            break;
        }
        // 3，开启输出模式
        RTMP_EnableWrite(rtmp);
        // 4，建立连接
        result = RTMP_Connect(rtmp, nullptr);
        if (!result) { // result == 0 和 ffmpeg不同，0代表失败
            LOGE("rtmp 建立连接失败:%d, url: %s", result, url);
            break;
        }
        // 5，连接流
        result = RTMP_ConnectStream(rtmp, 0);
        if (!result) { // result == 0 和 ffmpeg不同，0代表失败
            LOGE("rtmp 连接流失败");
            break;
        }
        start_time = RTMP_GetTime();
        // 准备好了，可以开始向服务器推流了
        readyPushing = true;
        // 队列开始工作
        packets.setWork(1);
        RTMPPacket *packet = nullptr;
        // 从队列里面获取压缩包(视频或音频)，直接发给服务器
        while (readyPushing) {
            packets.pop(packet); // 阻塞式
            if (!readyPushing) {
                break;
            }
            // 取不到数据，重新取，可能还没生产出来
            if (!packet) {
                continue;
            }
            // 到这里就是成功的获取队列的ptk了，可以发送给流媒体服务器
            packet->m_nInfoField2 = rtmp->m_stream_id;// 给rtmp的流id
            // 成功取出数据包，发送
            result = RTMP_SendPacket(rtmp, packet, 1); // 1==true 开启内部缓冲
            // packet 你都发给服务器了，可以大胆释放
            releasePackets(&packet);
            if (!result) { // result == 0 和 ffmpeg不同，0代表失败
                LOGE("rtmp 失败 自动断开服务器");
                break;
            }
        }
        releasePackets(&packet); // 只要跳出循环，就释放
    } while (false);
    // 本次一系列释放工作
    isStart = false;
    readyPushing = false;
    packets.setWork(0);
    packets.clear();
    if (rtmp) {
        RTMP_Close(rtmp);
        RTMP_Free(rtmp);
    }
    delete url;

    return nullptr;
}

/**
 * 开始直播 ---> 启动工作
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_ndk_push_NdkPusher_native_1start(JNIEnv *env, jobject thiz, jstring path_) {
    /**
     * 创建子线程：
     * 1.连接流媒体服务器；
     * 2.发包；
     */
    if (isStart) {
        return;
    }
    isStart = true;
    const char *path = env->GetStringUTFChars(path_, nullptr);
    // 深拷贝
    char *url = new char(strlen(path) + 1); // C++的堆区开辟 new -- delete
    strcpy(url, path);
    // 创建线程来进行直播
    pthread_create(&pid_start, nullptr, task_start, url);
    env->ReleaseStringUTFChars(path_, path); // 你随意释放，我已经深拷贝了
}

/**
 * 初始化x264编码器，Camera宽高改变，回调到这里，首次预览时触发
 * 给下面的pushVideo函数，去编码
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_ndk_push_NdkPusher_native_1initVideoEncoder(JNIEnv *env, jobject thiz, jint width,
                                                     jint height, jint fps, jint bitrate) {
    if (videoChannel) {
        videoChannel->initVideoEncoder(width, height, fps, bitrate);
    }
}

/**
 * Camera预览画面的数据，回调到这里，将原始数据进行x264编码后，得到的RTMPPkt(压缩数据)加入队列里面
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_ndk_push_NdkPusher_native_1pushVideo(JNIEnv *env, jobject thiz, jbyteArray data_) {
    if (!videoChannel || !readyPushing) { return; }
    // 把jni ---> C语言的
    jbyte *data = env->GetByteArrayElements(data_, nullptr);
    // data == nv21数据，编码，加入队列
    videoChannel->encodeData(data);
    env->ReleaseByteArrayElements(data_, data, 0); // 释放byte[]
}

// TODO >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 下面是 音频工作

/**
 * faac编码器 实例化成功
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_ndk_push_NdkPusher_native_1initAudioEncoder(JNIEnv *env, jobject thiz, jint sample_rate,
                                                     jint num_channels) {
    if (audioChannel) {
        audioChannel->initAudioEncoder(sample_rate, num_channels);
    }
}

/**
 * 获取 faac的样本数 给 Java层
 */
extern "C"
JNIEXPORT jint JNICALL
Java_com_ndk_push_NdkPusher_native_1getInputSamples(JNIEnv *env, jobject thiz) {
    if (audioChannel) {
        return audioChannel->getInputSamples();
    }
    return 0;
}

/**
 * 使用faac编码器，编码，封包，入队，使用start线程发送给流媒体服务器
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_ndk_push_NdkPusher_native_1pushAudio(JNIEnv *env, jobject thiz, jbyteArray data_) {
    if (!audioChannel || !readyPushing) {
        return;
    }
    jbyte *data = env->GetByteArrayElements(data_, nullptr); // 此data数据就是AudioRecord采集到的原始数据
    audioChannel->encodeData(data); // 核心函数：对音频数据 【进行faac的编码工作】
    env->ReleaseByteArrayElements(data_, data, 0); // 释放byte[]
}

// TODO >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 下面是 释放工作

extern "C"
JNIEXPORT void JNICALL
Java_com_ndk_push_NdkPusher_native_1stop(JNIEnv *env, jobject thiz) {
    isStart = false; // start的jni函数拦截的标记，为false
    readyPushing = false; // 九部曲推流的标记，为false
    packets.setWork(0); // 队列不准工作了
    pthread_join(pid_start, nullptr); // 稳稳等待start线程执行完成后，我在做后面的处理工作
}

extern "C"
JNIEXPORT void JNICALL
Java_com_ndk_push_NdkPusher_native_1release(JNIEnv *env, jobject thiz) {
    DELETE(videoChannel);
    DELETE(audioChannel);
}